/*
 * asscache.c
 *
 *  Created on: Apr 17, 2012
 *      Author: Tim Myers
 */

#include "cache.h"
#include "asscache.h"

uint32 numAssWays = 0;
uint32 numAssSets = 0;
uint32 numAssIndexBits = 0;
uint32 numAssTagBits = 0;
uint32 numAssBitsByteInWord = 2;
uint32 numAssBitsWordInBlock = 0;
uint32 assBlockSize = 0;

void updateLRU(asscacheset* AssCache, uint32 index, uint32 way)
{
	// Since all of the lru nodes in the list were malloced in one call, we can
	// access the one for a given way by the node list instead of searching for it
	lruNode* lruFound = AssCache[index].lruNodeList + way*sizeof(lruNode);

	// Get the nodes directly before and after the one node of the found access
	lruNode* lruFoundNext = lruFound->next;
	lruNode* lruFoundPrev = lruFound->prev;

	// Remove the found node from the middle of the list
	lruFoundPrev->next = lruFoundNext;
	lruFoundNext->prev = lruFoundPrev;

	// Move the found node to the beginning of the list
	lruNode* lruFirst = AssCache[index].firstLruNode;
	lruFound->next = lruFirst;
	lruFirst->prev = lruFound;
}

boolean createAssCache(cache_s* cache)
{
	printf("\nCreating the Ass Cache\n");

	switch(cache->level)
	{
		case L1I:
		case L1D:
			numAssSets = L1_cache_size / L1_block_size;
			numAssSets /= L1_assoc;

			numAssWays = L1_assoc;

			assBlockSize = L1_block_size;
			break;
		case L2:
			numAssSets = L2_cache_size / L2_block_size;
			numAssSets /= L2_assoc;

			numAssWays = L2_assoc;

			assBlockSize = L2_block_size;
			break;
		default:
			printf("Invalid Cache Level\n");
			return FALSE;
	}

	// Allocate space for all of the cache blocks
	asscacheset* newCache = (asscacheset*)malloc(numAssSets*sizeof(asscacheset));

	asscacheset* curCacheBlock = newCache;

	lruNode* lruNodePrev;
	lruNode* lruNodeNext;

	int i;
	for (i = 0; i < numAssSets; i++)
	{
		// Malloc space for the tags
		curCacheBlock->tags = (uint32*) malloc(numAssWays*sizeof(uint32));

		// Malloc space for the valid bits, set them all to 0
		curCacheBlock->valids = (boolean*) malloc(numAssWays*sizeof(boolean));
		memset(curCacheBlock->valids, 0, numAssWays*sizeof(boolean));

		// Malloc space for the dirty bits
		curCacheBlock->dirtys = (boolean*) malloc(numAssWays*sizeof(boolean));
		memset(curCacheBlock->dirtys, 0, numAssWays*sizeof(boolean));

		// Malloc space for the LRU structs
		lruNodePrev = (lruNode*) malloc(numAssWays*sizeof(lruNode));
		memset(lruNodePrev, 0, numAssWays*sizeof(lruNode));

		// Connect the LRU structs into a linked list, as well as set the cache sets'
		// first lru pointer
		lruNodePrev->way = 0;
		lruNodePrev->prev = NULL;

		curCacheBlock->firstLruNode = lruNodePrev;

		int j;
		for (j = 1; j < numAssWays-2; j++)
		{
			lruNodeNext = lruNodePrev + sizeof(lruNode);
			lruNodeNext->way = j;

			lruNodeNext->prev = lruNodePrev;
			lruNodePrev->next = lruNodeNext;

			lruNodePrev = lruNodeNext;
		}

		lruNodeNext = lruNodePrev + sizeof(lruNode);
		lruNodeNext->way = j;
		lruNodeNext->prev = lruNodePrev;
		lruNodeNext->next = NULL;

		curCacheBlock += sizeof(asscacheset);
	}

	// Get number of index bits
	uint32 temp = numAssSets;
	while (temp >>= 1)
	{
		numAssIndexBits++;
	}

	// Get number of bits for word in block
	temp = assBlockSize / WORD_SIZE;
	while (temp >>= 1)
	{
		numAssBitsWordInBlock++;
	}

	// Get number of tag bits
	numAssTagBits = WORD_SIZE - numAssIndexBits - numAssBitsWordInBlock - numAssBitsByteInWord;

	return TRUE;
}


boolean deleteAssCache(cache_s* cache)
{
	return TRUE;
}

boolean checkAssCache(cache_s* cache, uint32 reference, boolean isWrite)
{
	uint32 tag = reference >> (WORD_SIZE - numAssTagBits);

	uint32 index = reference >> (numAssBitsByteInWord + numAssBitsWordInBlock);
	index &= (0xFFFFFFFF >> (WORD_SIZE - numAssIndexBits));

	asscacheset* AssCache = (asscacheset*) cache->cacheptr;

	boolean found = FALSE;

	// Look through all of the ways to see if the reference is in the cache
	uint32 foundWay;
	int i;
	for (i = 0; i < numAssWays; i++)
	{
		if ( (AssCache[index].tags[i] == tag) && (AssCache[index].valids[i]) )
		{
			found = TRUE;
			foundWay = i;
		}
	}

	// Need to do some housekeeping if we find it in the cache on the initial check
	if (found)
	{
		updateLRU(AssCache, index, foundWay);

		// If access was a write, we need to set the dirty bit
		if (isWrite)
		{
			AssCache[index].dirtys[foundWay] = TRUE;
		}
	}

	return found;
}

kickout addValueToAssCache(cache_s* cache, uint32 reference, boolean isWrite)
{
	uint32 tag = reference >> (WORD_SIZE - numAssTagBits);

	uint32 index = reference >> (numAssBitsByteInWord + numAssBitsWordInBlock);
	index &= (0xFFFFFFFF >> (WORD_SIZE - numAssIndexBits));

	asscacheset* AssCache = (asscacheset*) cache->cacheptr;

	// Check to see if any of the way's have not yet been filled for this index
	boolean existsEmptyWay = FALSE;
	uint32 emptyWay;
	int i;
	for (i = 0; i < numAssWays; i++)
	{
		if (!AssCache[index].valids[i])
		{
			existsEmptyWay = TRUE;
			emptyWay = i;
		}
	}

	kickout ret;

	// If there was an empty way, adding value to cache is trivial
	if (existsEmptyWay)
	{
		AssCache[index].tags[emptyWay] = tag;
		AssCache[index].valids[emptyWay] = TRUE;

		if (isWrite)
		{
			AssCache[index].dirtys[emptyWay] = TRUE;
		}
		else
		{
			AssCache[index].dirtys[emptyWay] = FALSE;
		}

		updateLRU(AssCache, index, emptyWay);

		ret.type = NOKICK;
		ret.evictedAddress = 0;
	}
	// If there is no room, we have to replace the least recently used way
	else
	{
		uint32 lruWay = AssCache[index].firstLruNode->way;

		uint32 evictedTag = AssCache[index].tags[lruWay];

		// Build up the reference being evicted
		uint32 evictedAddress = evictedTag << (WORD_SIZE - numAssTagBits);
		evictedAddress |= index;
		evictedAddress <<= numAssIndexBits;
		// Don't care about the word in the block

		ret.evictedAddress = evictedAddress;

		// Check if the least recently used way is dirty
		if (AssCache[index].dirtys[lruWay])
		{
			ret.type = DIRTY;
		}
		else
		{
			ret.type = CLEAN;
		}

		// Update the block with the new address
		AssCache[index].tags[lruWay] = tag;
		AssCache[index].valids[lruWay] = TRUE;

		if (isWrite)
		{
			AssCache[index].dirtys[lruWay] = TRUE;
		}
		else
		{
			AssCache[index].dirtys[lruWay] = FALSE;
		}

		updateLRU(AssCache, index, lruWay);
	}


	return ret;
}
